package messages;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;

import state.Globals;
import state.group.ElevatorStruct;

import messages.elevator.ElevatorStatusMessage;
import messages.elevator.ElevatorStatusRequest;
import messages.elevator.HallCallAssignmentMessage;
import messages.elevator.HallCallRevokeMessage;
import messages.observer.CurrentStateMessage;
import messages.observer.CurrentStateRequest;
import messages.observer.EventCollectionMessage;
import messages.persondispatcher.CurrentPeopleStateMessage;
import messages.persondispatcher.CurrentPeopleStateRequest;
import messages.persondispatcher.PersonCreationMessage;
import messages.persondispatcher.RegisterHallCallMessage;
import messages.registration.ElevatorRegistrationMessage;
import messages.registration.ElevatorRegistrationMessageAck;
import messages.registration.ObserverRegistrationMessage;
import messages.registration.PersonDispatcherRegistrationMessage;
import messages.udp.PersonInElevatorMessage;
import messages.udp.PersonPressButtonMessage;
import messages.udp.PersonTransferMessage;

/**
 * This is the Factory class for parsing messages out of byte arrays.
 * 
 * @author Ian Doerr
 */
public class MessageFactory {

    /**
     * Performs the actual parsing of messages, and maps them to their related objects
     * 
     * @param data
     * @return
     */
    public static BaseMessage parseMessage(byte[] data)
    {
        if (data == null || data.length == 0)
        {
        	return null;
        }
        
        MessageType type = MessageType.getMessageTypeForId(data[0]);
        
        if (type == null)
        {
            return null;
        }

        try
        {
        switch (type)
        {
            case DeregistrationMessage:
                return new DeregistrationMessage(data);
            case ElevatorStatusMessage:
                return new ElevatorStatusMessage(data);
            case ElevatorStatusRequest:
                return new ElevatorStatusRequest(data);
            case HallCallAssignmentMessage:
                return new HallCallAssignmentMessage(data);
            case HallCallRevokeMessage:
                return new HallCallRevokeMessage(data);
            case CurrentStateMessage:
                return new CurrentStateMessage(data);
            case CurrentStateRequest:
                return new CurrentStateRequest(data);
            case EventCollectionMessage:
                return new EventCollectionMessage(data);
            case RegisterHallCallMessage:
                return new RegisterHallCallMessage(data);
            case CurrentPeopleStateMessage:
                return new CurrentPeopleStateMessage(data);
            case CurrentPeopleStateRequest:
                return new CurrentPeopleStateRequest(data);
            case ElevatorRegistrationMessage:
                return new ElevatorRegistrationMessage(data);
            case ElevatorRegistrationMessageAck:
                return new ElevatorRegistrationMessageAck(data);
            case ObserverRegistrationMessage:
                return new ObserverRegistrationMessage(data);
            case PersonDispatcherRegistrationMessage:
                return new PersonDispatcherRegistrationMessage(data);
            case PersonTransferMessage:
                return new PersonTransferMessage(data);
            case GeneralAck:
                return new GeneralAck(data);
            case PersonInElevatorMessage:
                return new PersonInElevatorMessage(data);
            case PersonPressButtonMessage:
                return new PersonPressButtonMessage(data);
            case PersonCreationMessage:
            	return new PersonCreationMessage(data);
            case KillOrderMessage:
            	return new KillOrderMessage(data);
            default:
                throw new MessageTypeNotFoundException();
        }
        }
        catch (RuntimeException e)
        {
            if (e.getClass().getName().equals("java.lang.RuntimeException"))
            {
            	Globals.getLogger().log("Received an invalid message with data: " + MessageUtils.byteArrayToString(data));
            	
                return null;
            }
            else
            {
                throw (e);
            }
        }
    }
    
    /**
     * Reads a message from a socket, and returns it.
     * Uses a default timeout.
     * 
     * @param remoteSock
     * @return
     * @throws IOException
     */
    public static BaseMessage readMessage(Socket remoteSock) throws IOException
    {
    	return readMessage(remoteSock, 2);
    }
    
    /**
     * Reads a message from a socket.
     * Uses the passed timeout.
     * 
     * @param remoteSock
     * @param numSecondsToWait
     * @return
     * @throws IOException
     */
    public static BaseMessage readMessage(Socket remoteSock, int numSecondsToWait) throws IOException
    {
        if (!remoteSock.isConnected())
        {
            return null;
        }
        
        InputStream stream = remoteSock.getInputStream();
        
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        byte[] buf = new byte[ElevatorStruct.MAX_MESSAGE_SIZE];
        
        remoteSock.setSoTimeout(numSecondsToWait * 1000);
        
        do
        {
            int bytesRead = stream.read(buf);
            if (bytesRead == -1)
            {
                throw new IOException ("Socket closed!");
            }
            buffer.write(buf, 0, bytesRead);
        }
        while (stream.available() > 0);
        
        if (Globals.isDebug())
        {
	    	Globals.getLogger().log("MessageFactory attempting to parse: " + MessageUtils.byteArrayToString(buffer.toByteArray()));
        }
        
        return MessageFactory.parseMessage(buffer.toByteArray());
    }

}
